/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bean.session;

import entity.BackorderEntity;
import entity.BillOfMaterialEntity;
import entity.BomRawMaterialWeightEntity;
import entity.CompletedGoodsEntity;
import entity.MasterProductionScheduleDailyEntity;
import entity.MrpToQuantityEntity;
import entity.PersonEntity;
import entity.ProductPercentageQuantityEntity;
import entity.ProductQuantityPriceEntity;
import entity.RawMaterialEntity;
import entity.RawMaterialRequirementWeeklyEntity;
import entity.SalesOrderEntity;
import entity.SalesQuotationEntity;
import entity.ShippingRequestEntity;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import type.AtpCheckStatus;
import type.BackorderStatus;
import type.InternalStatus;
import type.Month;

/**
 *
 * @author wei
 */
@Stateless
public class BackorderEntityFacade extends AbstractFacade<BackorderEntity> {

    @PersistenceContext(unitName = "MacTeam-warPU")
    private EntityManager em;
    //@EJB private bean.session.MasterProductionScheduleDailyEntityFacade dailyFacade;
    @Resource
    SessionContext ctx;

    protected EntityManager getEntityManager() {
        return em;
    }

    public BackorderEntityFacade() {
        super(BackorderEntity.class);
    }

    public BackorderEntity atpCheckForMRP(BackorderEntity backOrder, Collection<RawMaterialEntity> list, PersonEntity person) {



        // raw Material ATP check
        Date requiredDate = backOrder.getRequiredLeadTime();
        System.out.println("requiredDate to string " + requiredDate.toString());
//      Calendar testDate = new GregorianCalendar(2011, Calendar.NOVEMBER, 18);
        Calendar testDate = Calendar.getInstance();
        Date currentDate = testDate.getTime();
        backOrder.setLastModifiedAt(currentDate);
//      backOrder.modified(person);

        Calendar preferedDate = Calendar.getInstance();
        Calendar today = Calendar.getInstance();

        preferedDate.setTime(requiredDate);
        today.setTime(currentDate);

        int requiredYear = preferedDate.get(Calendar.YEAR);
        int currentYear = today.get(Calendar.YEAR);


        Month requiredMonth = Month.fromMonthNum(preferedDate.get(Calendar.MONTH) + 1);
        Month currentMonth = Month.fromMonthNum(today.get(Calendar.MONTH) + 1);

        //for week checking

        int requiredWeek = preferedDate.get(Calendar.WEEK_OF_MONTH);
        int currentWeek = today.get(Calendar.WEEK_OF_MONTH);

        Calendar testPrefer = Calendar.getInstance();
        Calendar testCurrent = Calendar.getInstance();
        testPrefer.set(requiredYear, preferedDate.get(Calendar.MONTH), 1);
        testCurrent.set(currentYear, today.get(Calendar.MONTH), 1);

        if (!checkFirstDayOfMonthIsMondayOrSunday(testPrefer)) {
            requiredWeek -= 1;  // actual working week of the month
            if (requiredWeek == 0) { // means it's previous month's last week
                requiredWeek = findPreviousMonthLastWeekNum(testPrefer);
                if (requiredMonth != Month.JANUARY) {
                    requiredMonth = Month.fromMonthNum(requiredMonth.getMonthNum() - 1);
                } else {
                    requiredYear -= 1;
                    requiredMonth = Month.DECEMBER;
                }


            }
            System.out.println("requiredWeekNum is " + requiredWeek);
        } else {
            System.out.println("requiredWeekNum is " + requiredWeek);
        }

        if (!checkFirstDayOfMonthIsMondayOrSunday(testCurrent)) {
            currentWeek -= 1;
            if (currentWeek == 0) { // means it's previous month's last week
                currentWeek = findPreviousMonthLastWeekNum(testCurrent);
                if (currentMonth != Month.JANUARY) {
                    currentMonth = Month.fromMonthNum(currentMonth.getMonthNum() - 1);
                } else {
                    currentYear -= 1;
                    currentMonth = Month.DECEMBER;
                }
            }
            System.out.println("currentWeekNum is " + currentWeek);
        } else {
            System.out.println("currentWeekNum is " + currentWeek);
        }

        System.out.println("prefered leadTime info: Year " + requiredYear + " Month " + requiredMonth + " Week " + requiredWeek + " ");
        System.out.println("current Date info: Year " + currentYear + " Month " + currentMonth + " Week " + currentWeek + " ");

        ArrayList<RawMaterialEntity> materialList = new ArrayList(list);
//       System.out.println("materialList.size() "+materialList.size());
        boolean setPass = true;

        for (RawMaterialEntity r : materialList) {
            if (!checkAtpMrp(r, backOrder, currentYear, currentMonth, currentWeek, requiredYear, requiredMonth, requiredWeek)) {
                // setATPcheck for back order status as failed

                setPass = false;

            }

        }
        if (setPass == true) {
            backOrder.setMrpCheckStatus(AtpCheckStatus.pass);
            System.out.println("pass liao la");
            backOrder.setEarliestMrpFulfillmentDate(calculateEarliestFulfillmentDateVersion2(backOrder, materialList));
        } else {
            backOrder.setMrpCheckStatus(AtpCheckStatus.fail);
            backOrder.setEarliestMrpFulfillmentDate(calculateEarliestFulfillmentDateMRP(backOrder, materialList));
        }

        reserveBackOrder(backOrder, list);
        System.out.println("the earliest fulfill time " + backOrder.getEarliestMrpFulfillmentDate());
        System.out.println(backOrder.getFailedAtpMrps().size() + " failAtpSize");
        backOrder.setStatus(BackorderStatus.CustomerVerificationPending);
        em.merge(backOrder);
        scheduleTimer(backOrder.getId());
        return backOrder;
    }

    public boolean checkFirstDayOfMonthIsMondayOrSunday(Calendar ca) {
        String[] strDays = new String[]{"Sunday", "Monday", "Tuesday",
            "Wednesday", "Thusday",
            "Friday", "Saturday"};
        String day = strDays[ca.get(Calendar.DAY_OF_WEEK) - 1];
//        System.out.println(day+"jjj");
        if (day.equalsIgnoreCase("Monday") || day.equalsIgnoreCase("Sunday")) {
            return true;
        }
        return false;
    }

    private Boolean checkAtpMrp(RawMaterialEntity r, BackorderEntity backOrder, int currentYear, Month currentMonth, int currentWeek, int requiredYear, Month requiredMonth, int requiredWeek) {
//        HashMap<RawMaterialRequirementWeeklyEntity,Long> map = new HashMap<RawMaterialRequirementWeeklyEntity,Long>();

        RawMaterialEntity material = r;
        int leadTime = material.getLeadTime();
        long grossRequirement = calculateGrossRequirement(backOrder, material);

        if (grossRequirement == 0) { // no need to purchase for this raw material
            return true;
        } //        else if(findMrp(requiredYear,requiredMonth,requiredWeek,material)==null){// if the target week mrp is not created, means it's in future, so pass
        //            return true;
        //            
        //        }
        else {

            RawMaterialRequirementWeeklyEntity currentMrp = findMrp(currentYear, currentMonth, currentWeek, material);
//            System.out.println("test get current mrp "+ mrp.getGrossRequirement()+" "+mrp.getRawMaterial().getProductName());
            ArrayList<RawMaterialRequirementWeeklyEntity> inBetweenMrps = findMrpsInBetween(currentMrp);
//              System.out.println("inBetweenMrps.size() "+inBetweenMrps.size());
            int startIndex = 0;
            boolean betweenMrpsContainTargetWeek = false;
            for (int q = 0; q < inBetweenMrps.size(); q++) {
                RawMaterialRequirementWeeklyEntity d = inBetweenMrps.get(q);
                int year = d.getYearOfProduction();
                Month month = d.getMonthOfProduction();
                int week = d.getWeekOfProduction();

                if (year == requiredYear && month == requiredMonth && week == requiredWeek) {
                    betweenMrpsContainTargetWeek = true;
                    startIndex = q;
                    break;
                }

            }

            if (betweenMrpsContainTargetWeek == false) { // the target recieving is in futrue, can be fulfilled by planned order
                System.out.println("betweenMrpsContainTargetWeek is false " + currentMrp.getRawMaterial());
                return true;
            } else {
                ArrayList<RawMaterialRequirementWeeklyEntity> testMrpList = new ArrayList<RawMaterialRequirementWeeklyEntity>();
//                  System.out.println("Start Index "+startIndex);
//                      System.out.println("inBetweenMrps.size() "+inBetweenMrps.size());
                for (int z = startIndex; z < inBetweenMrps.size(); z++) {

                    testMrpList.add(inBetweenMrps.get(z)); // testMrpLIst contains the mrps starting from target week til the week that would be affected without getting reciept
                }
                return checkDeductOnHand(backOrder, testMrpList, grossRequirement);
            }
        }




    }

    private long calculateGrossRequirement(BackorderEntity backOrder, RawMaterialEntity material) {
        long requirement = 0;
        ArrayList<ProductQuantityPriceEntity> productList = new ArrayList<ProductQuantityPriceEntity>(backOrder.getProductQuantityPriceList());
        for (ProductQuantityPriceEntity p : productList) {

            requirement += calculateRawMaterialRequirement(p, material);
        }
//        System.out.println("gross requirement for "+material.getProductName()+" is "+requirement);
        return requirement;
    }

    private long calculateRawMaterialRequirement(ProductQuantityPriceEntity p, RawMaterialEntity material) {
        long requirement = 0;

        CompletedGoodsEntity c = (CompletedGoodsEntity) p.getProduct();
        BillOfMaterialEntity bom = c.getBom();
        ArrayList<BomRawMaterialWeightEntity> breakDowns = new ArrayList<BomRawMaterialWeightEntity>(bom.getRawMaterialBreakDowns());
        double weight = 0;
        for (BomRawMaterialWeightEntity b : breakDowns) {
//                 System.out.println(b.getRawMaterial().getProductName());
            if (b.getRawMaterial().getProductName().equals(material.getProductName())) {

                weight = b.getBomWeight();
//                     System.out.println(weight);
            }
        }
        requirement += weight * p.getQuantity() * c.getWeightPerCase();

//                  System.out.println("weight is "+weight);
//                  System.out.println("quantity is "+p.getQuantity());
//                  System.out.println("weightpercase is "+c.getWeightPerCase());
//                  System.out.println("individual requirement is "+ requirement);
        return requirement;
    }

    public RawMaterialRequirementWeeklyEntity findMrp(int currentYear, Month currentMonth, int currentWeek, RawMaterialEntity material) {
        Query query = (Query) em.createQuery("Select r from RawMaterialRequirementWeekly r WHERE r.yearOfProduction=:yearOfProduction and r.monthOfProduction=:monthOfProduction and r.weekOfProduction=:week");
        query.setParameter("yearOfProduction", currentYear);
        query.setParameter("monthOfProduction", currentMonth);
        query.setParameter("week", currentWeek);
        RawMaterialRequirementWeeklyEntity currentMrp = new RawMaterialRequirementWeeklyEntity();

        try {
            List<RawMaterialRequirementWeeklyEntity> list = (List<RawMaterialRequirementWeeklyEntity>) query.getResultList();
            List<RawMaterialRequirementWeeklyEntity> mrpList2 = new ArrayList<RawMaterialRequirementWeeklyEntity>();
            for (RawMaterialRequirementWeeklyEntity k : list) {
                if (!k.isArchived()) {
                    mrpList2.add(k);
                }
            }
            for (RawMaterialRequirementWeeklyEntity t : mrpList2) {
                if (t.getRawMaterial().getProductName().equals(material.getProductName())) {
                    return t;
                }
            }
        } catch (NoResultException e) {
            System.out.println("search mrp " + currentYear + " " + currentMonth + " " + currentWeek);
            return null;
        }
        return null;
    }

    private ArrayList<RawMaterialRequirementWeeklyEntity> findMrpsInBetween(RawMaterialRequirementWeeklyEntity currentMrp) { // including current week
        ArrayList<RawMaterialRequirementWeeklyEntity> mrps = new ArrayList<RawMaterialRequirementWeeklyEntity>();
        mrps.add(currentMrp);
//        System.out.println("currentWeekNumber now "+currentMrp.getWeekOfProduction()+" "+currentMrp.getRawMaterial().getProductName()+"leadTime "+currentMrp.getRawMaterial().getLeadTime());
        RawMaterialRequirementWeeklyEntity nextMrp = new RawMaterialRequirementWeeklyEntity();
//        System.out.println("currentMrp.year "+currentMrp.getYearOfProduction());
//        System.out.println("currentMrp.month "+currentMrp.getMonthOfProduction());

        int leadTime = currentMrp.getRawMaterial().getLeadTime();
        for (int i = leadTime - 1; i > 0; i--) { // don't need to consider the planned recieving week,since what's short of can be always purchased this week

            nextMrp = findNextWeekMrp(currentMrp);
            mrps.add(nextMrp);
            currentMrp = nextMrp;
//            System.out.println("nextWeekNumber add "+currentMrp.getWeekOfProduction()+" ");
        }
        return mrps;
    }

    private RawMaterialRequirementWeeklyEntity findNextWeekMrp(RawMaterialRequirementWeeklyEntity currentMrp) {
        RawMaterialEntity material = currentMrp.getRawMaterial();
        int currentYear = currentMrp.getYearOfProduction();
        Month currentMonth = currentMrp.getMonthOfProduction();
        int currentWeek = currentMrp.getWeekOfProduction();
        RawMaterialRequirementWeeklyEntity nextMrp = null;

//       System.out.println("hahahaha"+currentMrp.getYearOfProduction()+" "+currentMrp.getMonthOfProduction()+" "+currentMrp.getWeekOfProduction()+" ");

        if (currentMrp.getIsLastWeek()) {

            if (currentMonth == Month.DECEMBER) {
                nextMrp = findMrp(currentYear + 1, Month.JANUARY, 1, material);

            } else {
                nextMrp = findMrp(currentYear, Month.fromMonthNum(currentMonth.getMonthNum() + 1), 1, material);
            }

        } else {

            nextMrp = findMrp(currentYear, currentMonth, (currentWeek + 1), material);


        }
        return nextMrp;
    }

    private boolean checkDeductOnHand(BackorderEntity backOrder, ArrayList<RawMaterialRequirementWeeklyEntity> testMrpList, long grossRequirement) {
//         HashMap<RawMaterialRequirementWeeklyEntity,Long> map = new HashMap<RawMaterialRequirementWeeklyEntity,Long>();
        boolean canDeduct = true;

        for (RawMaterialRequirementWeeklyEntity r : testMrpList) {
            System.out.println("checkDeductOnHand " + r.getRawMaterial().getProductName() + " " + r.getYearOfProduction() + " " + r.getMonthOfProduction() + " " + r.getWeekOfProduction() + " ");
            long onHand = r.getOnHandQuantity() - r.getMrpReservation();
            System.out.println("onHand - reservation" + onHand);
            System.out.println("grossRequirement " + grossRequirement);
            if (onHand - grossRequirement < 0) {
                canDeduct = false;
                long shortFall = grossRequirement - onHand;
                MrpToQuantityEntity mtq = new MrpToQuantityEntity(r, shortFall);
                em.persist(mtq);
                backOrder.getFailedAtpMrps().add(mtq);

//                map.put(r, shortFall); 

            }
        }
        return canDeduct;
    }

    private Date calculateEarliestFulfillmentDateMRP(BackorderEntity backOrder, ArrayList<RawMaterialEntity> materialList) {
        Date earliest;
        Calendar cal = Calendar.getInstance();
//        cal.setTime(earliest);
        long grossRequirement = 0;
        int longestLeadTime = 0;
        int daysRequired = 0;

        for (RawMaterialEntity material : materialList) {
            grossRequirement = calculateGrossRequirement(backOrder, material);

            if (grossRequirement != 0) {
                if (material.getLeadTime() > longestLeadTime) {
                    longestLeadTime = material.getLeadTime();
                }
            }
        }

        daysRequired = (longestLeadTime * 7);// extend one week for safety purpose
        cal.add(Calendar.DATE, daysRequired);

        if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
            cal.add(Calendar.DATE, 2);// shift to monday as the working day
        }
        if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
            cal.add(Calendar.DATE, 3);// shift to monday as the working day
        }

        earliest = cal.getTime();
        return earliest;
    }

    public void updateMrpForBackOrder(BackorderEntity current, Collection<RawMaterialEntity> materialList, PersonEntity person) {
        Date confirmedDate = current.getConfirmedLeadTime();
        confirmedDate = current.getConfirmedLeadTime(); // for testing purpose

//        System.out.println("confirmedDate toString "+ confirmedDate.toString());

        ArrayList<BackorderEntity> updateAtpMrps = new ArrayList<BackorderEntity>();

        // find year, month, week of confirmed date
        Calendar deliveryDate = Calendar.getInstance();
        deliveryDate.setTime(confirmedDate);

        int deliveryYear = deliveryDate.get(Calendar.YEAR);
        Month deliveryMonth = Month.fromMonthNum(deliveryDate.get(Calendar.MONTH) + 1);
//        System.out.println("deliveryMonth is "+ deliveryMonth);


        //for week checking
        int deliveryWeek = deliveryDate.get(Calendar.WEEK_OF_MONTH);


        Calendar testDelivery = Calendar.getInstance();

        testDelivery.set(deliveryYear, deliveryDate.get(Calendar.MONTH), 1);


        if (!checkFirstDayOfMonthIsMondayOrSunday(testDelivery)) {
            deliveryWeek -= 1;  // actual working week of the month
            if (deliveryWeek == 0) { // means it's previous month's week 5
                deliveryWeek = 5;
                if (deliveryMonth != Month.JANUARY) {

                    deliveryMonth = Month.fromMonthNum(deliveryMonth.getMonthNum() - 1);
                } else {
                    deliveryYear -= 1;
                    deliveryMonth = Month.DECEMBER;
                }


            }
            System.out.println("deliveryWeekNum is " + deliveryWeek);
        } else {
            System.out.println("deliveryWeekNum is " + deliveryWeek);
        }

        System.out.println("confirmed leadTime info: Year " + deliveryYear + " Month " + deliveryMonth + " Week " + deliveryWeek + " ");

        if (!current.getUpdatedAtpMrps().isEmpty()) {
            current.setUpdatedAtpMrps(new ArrayList<MrpToQuantityEntity>());
        }
        for (RawMaterialEntity material : materialList) {

            recommendUpdateMrpForBackOrderByMaterial(current, material, deliveryYear, deliveryMonth, deliveryWeek, person);
        }
//        current.modified(person);
        current.setStatus(BackorderStatus.Confirmed);
        em.merge(current);
    }

    private void recommendUpdateMrpForBackOrderByMaterial(BackorderEntity current, RawMaterialEntity material, int deliveryYear, Month deliveryMonth, int deliveryWeek, PersonEntity person) {
        RawMaterialRequirementWeeklyEntity orderingWeek = findOrderWeekMrp(deliveryYear, deliveryMonth, deliveryWeek, material);
//        RawMaterialRequirementWeeklyEntity currentWeek = findMrp(deliveryYear,deliveryMonth,deliveryWeek,material);


        if (orderingWeek == null) {
            // means mrp is not created, can wait for it to be created based on mps in future
        } else if (alreadyInThePast(orderingWeek)) {
            // supposed to order week is already in the past, place order this week
            orderingWeek = findCurrentWeekMrp(material);
        } else {
            long grossRequirement = calculateGrossRequirement(current, material);

            int lotSize = material.getLotSize();
            long requiredPurchase = (long) ((Math.floor(grossRequirement / lotSize) + 1) * lotSize);
            long oldPlannedOrder = orderingWeek.getPlannedOrderQuantity();
//            orderingWeek.setPlannedOrderQuantity(oldPlannedOrder+grossRequirement);
            MrpToQuantityEntity mtq = new MrpToQuantityEntity(orderingWeek, requiredPurchase);
//            if(currentWeek!=null){
//                currentWeek.setPlannedReceipt(currentWeek.getPlannedReceipt()+grossRequirement);
////                currentWeek.modified(person);
//            }
////            orderingWeek.modified(person);
//          
//            RawMaterialRequirementWeeklyEntity rawMaterialRecievingWeek = findRecievingWeek(orderingWeek, material);// find the recieving week mrp based on planned order week
//            if(rawMaterialRecievingWeek!=null){
//               rawMaterialRecievingWeek.setPlannedReceipt(rawMaterialRecievingWeek.getPlannedReceipt()+grossRequirement);               
//            }


            Collection<MrpToQuantityEntity> list = current.getUpdatedAtpMrps();
            list.add(mtq);
            em.persist(mtq);
            current.setUpdatedAtpMrps(list);

        }

    }

    public RawMaterialRequirementWeeklyEntity findOrderWeekMrp(int deliveryYear, Month deliveryMonth, int deliveryWeek, RawMaterialEntity material) {
        RawMaterialRequirementWeeklyEntity targetMrp = new RawMaterialRequirementWeeklyEntity();
        int leadTime = material.getLeadTime();
        int weekNumber;


        if (deliveryWeek > leadTime) { // the week that planned order made is in the same month
            weekNumber = deliveryWeek - leadTime;
            targetMrp = findMrp(deliveryYear, deliveryMonth, weekNumber, material);

        } else {
            int weekNumberToCountBack = leadTime - deliveryWeek;
            int weekNumberOfPreviousMonthLastWeek;

            int searchYear;
            Month searchMonth;
            if (deliveryMonth == Month.JANUARY) {
                searchYear = deliveryYear - 1;
                searchMonth = Month.DECEMBER;


            } else {
                searchYear = deliveryYear;
                searchMonth = Month.fromMonthNum(deliveryMonth.getMonthNum() - 1);

            }
            weekNumberOfPreviousMonthLastWeek = searchLastWeekNumber(searchYear, searchMonth);
            weekNumber = weekNumberOfPreviousMonthLastWeek - weekNumberToCountBack;
            targetMrp = findMrp(searchYear, searchMonth, weekNumber, material);

        }

        return targetMrp;

    }

    private int searchLastWeekNumber(int year, Month month) {

        boolean isLastWeek = true;

        Query query = (Query) em.createQuery("Select r from RawMaterialRequirementWeekly r WHERE r.yearOfProduction=:yearOfProduction and r.monthOfProduction=:monthOfProduction and r.isLastWeek=:lastWeek");
        query.setParameter("yearOfProduction", year);
        query.setParameter("monthOfProduction", month);
        query.setParameter("lastWeek", isLastWeek);
        RawMaterialRequirementWeeklyEntity plannedOrderWeek = new RawMaterialRequirementWeeklyEntity();

        try {
            if (query.getResultList().isEmpty()) {
                return 100;
            }
            plannedOrderWeek = (RawMaterialRequirementWeeklyEntity) query.getResultList().get(0);
//            System.out.println("List.size is "+mpsList.size());
        } catch (NoResultException e) {
            System.out.println("search previous month last week number found no result " + year + " " + month + " ");
        }
        return plannedOrderWeek.getWeekOfProduction();
    }

    private boolean alreadyInThePast(RawMaterialRequirementWeeklyEntity orderingWeek) {

        boolean inThePast = false;
        int orderYear = orderingWeek.getYearOfProduction();
        Month orderMonth = orderingWeek.getMonthOfProduction();
        int orderWeek = orderingWeek.getWeekOfProduction();

        RawMaterialRequirementWeeklyEntity currentMrp = findCurrentWeekMrp(orderingWeek.getRawMaterial());
        int currentYear = currentMrp.getYearOfProduction();
        Month currentMonth = currentMrp.getMonthOfProduction();
        int currentWeek = currentMrp.getWeekOfProduction();

        if (orderYear < currentYear) {
            inThePast = true;
        } else {

            if ((orderYear == currentYear) && (orderMonth.getMonthNum() < currentMonth.getMonthNum())) {
                inThePast = true;
            } else {
                if ((orderYear == currentYear) && (orderMonth.getMonthNum() == currentMonth.getMonthNum()) && (orderWeek < currentWeek)) {
                    inThePast = true;
                }
            }
        }

        return inThePast;

    }

    private RawMaterialRequirementWeeklyEntity findCurrentWeekMrp(RawMaterialEntity material) {
        Calendar today = Calendar.getInstance();
        int currentYear = today.get(Calendar.YEAR);
        Month currentMonth = Month.fromMonthNum(today.get(Calendar.MONTH) + 1);
        int currentWeek = today.get(Calendar.WEEK_OF_MONTH);
        Calendar testCurrent = Calendar.getInstance();
        testCurrent.set(currentYear, today.get(Calendar.MONTH), 1);
        if (!checkFirstDayOfMonthIsMondayOrSunday(testCurrent)) {
            currentWeek -= 1;
            if (currentWeek == 0) { // means it's previous month's last week
                currentWeek = findPreviousMonthLastWeekNum(testCurrent);
                if (currentMonth != Month.JANUARY) {
                    currentMonth = Month.fromMonthNum(currentMonth.getMonthNum() - 1);
                } else {
                    currentYear -= 1;
                    currentMonth = Month.DECEMBER;
                }
            }
//          System.out.println("currentWeekNum is "+currentWeek);
        } else {
//           System.out.println("currentWeekNum is "+currentWeek);
        }

        return findMrp(currentYear, currentMonth, currentWeek, material);
    }

    private RawMaterialRequirementWeeklyEntity findRecievingWeek(RawMaterialRequirementWeeklyEntity orderingWeek, RawMaterialEntity material) {
        int leadTime = material.getLeadTime();
        int orderYear = orderingWeek.getYearOfProduction();
        Month orderMonth = orderingWeek.getMonthOfProduction();
        int orderWeek = orderingWeek.getWeekOfProduction();

        int recievingYear;
        Month recievingMonth;
        int recievingWeek;

        int lastWeekNumber = searchLastWeekNumber(orderYear, orderMonth);


        if (orderWeek + leadTime <= lastWeekNumber) {
            recievingYear = orderYear;
            recievingMonth = orderMonth;
            recievingWeek = orderWeek + leadTime;
        } else {
            recievingWeek = orderWeek + leadTime - lastWeekNumber;
            if (orderMonth == Month.DECEMBER) {
                recievingYear = orderYear + 1;
                recievingMonth = Month.JANUARY;
            } else {
                recievingYear = orderYear;
                recievingMonth = Month.fromMonthNum(orderMonth.getMonthNum() + 1);
            }
        }

        return findMrp(recievingYear, recievingMonth, recievingWeek, material);
    }

    public BackorderEntity updateMrpPlannedOrders(BackorderEntity current) {
        current.setIsMrpUpdated(true);
        ArrayList<MrpToQuantityEntity> mtqList = new ArrayList<MrpToQuantityEntity>(current.getUpdatedAtpMrps());

        for (MrpToQuantityEntity mtq : mtqList) {
            RawMaterialRequirementWeeklyEntity orderingWeek = mtq.getMrp();
            long extraOrder = (long) mtq.getQuantity();

            orderingWeek.setPlannedOrderQuantity(extraOrder + orderingWeek.getPlannedOrderQuantity());
            em.merge(orderingWeek);
            RawMaterialRequirementWeeklyEntity rawMaterialRecievingWeek = findRecievingWeek(orderingWeek, orderingWeek.getRawMaterial());// find the recieving week mrp based on planned order week
            if (rawMaterialRecievingWeek != null) {
                rawMaterialRecievingWeek.setPlannedReceipt(rawMaterialRecievingWeek.getPlannedReceipt() + extraOrder);
                em.merge(rawMaterialRecievingWeek);
            }
        }

        current.setUpdatedAtpMrps(mtqList);
        em.merge(current);
        return current;
    }

    public int findPreviousMonthLastWeekNum(Calendar testPrefer) {

        Calendar testPreviousMonthLastDay = Calendar.getInstance();
        Calendar previousMonthLastDay = Calendar.getInstance();
        testPreviousMonthLastDay.set(testPrefer.get(Calendar.YEAR), testPrefer.get(Calendar.MONTH) - 1, 1);
        int maxDay = testPreviousMonthLastDay.getActualMaximum(Calendar.DAY_OF_MONTH);
        previousMonthLastDay.set(testPrefer.get(Calendar.YEAR), testPrefer.get(Calendar.MONTH) - 1, maxDay);
        int requiredWeek = previousMonthLastDay.get(Calendar.WEEK_OF_MONTH);


        if (!checkFirstDayOfMonthIsMondayOrSunday(testPreviousMonthLastDay)) {
            requiredWeek -= 1;  // actual working week of the month
            System.out.println("find previous month last week num requiredWeekNum is " + requiredWeek);
        } else {
            System.out.println("find previous month last week num is " + requiredWeek);
        }

        return requiredWeek;
    }

    public Date atpCheckForProduction(BackorderEntity backOrder, Date earliestRMReceipt) {
        Date earliest = new Date();
        System.out.println("ATP: Preparing ATP check...");
        //holding list to reset the revised values back to the backorder's original values
        ArrayList<Long> quantity = new ArrayList<Long>();
        ArrayList<Double> utilNorm = new ArrayList<Double>();
        ArrayList<Double> utilOT = new ArrayList<Double>();

        //the earliest date production may start after ordering the goods
        Date productionStartDate = getProductionStartDate(earliestRMReceipt);
        //get all daily mps from that day onwards
        ArrayList<MasterProductionScheduleDailyEntity> dailyMPS = getAllDailyMPSFromDateOnwards(productionStartDate);
        //get pqp of back order
        ArrayList<ProductQuantityPriceEntity> pqp = new ArrayList<ProductQuantityPriceEntity>(backOrder.getProductQuantityPriceList());
        //store them into a holding list
        for (int i = 0; i < pqp.size(); i++) {
            long q = pqp.get(i).getQuantity();
            quantity.add(q);
        }
        for (int h = 0; h < dailyMPS.size(); h++) {
            double norm = dailyMPS.get(h).getUtilizationNormal();
            double ot = dailyMPS.get(h).getUtilizationOverTime();
            utilNorm.add(norm);
            utilOT.add(ot);
        }
        earliest = assignReservedCapacity(backOrder, pqp, dailyMPS);
        backOrder.setEarliestSopFulfillmentDate(assignReservedCapacity(backOrder, pqp, dailyMPS));
        //reset all values changed
        for (int i = 0; i < quantity.size(); i++) {
            pqp.get(i).setQuantity(quantity.get(i));
        }
        for (int j = 0; j < dailyMPS.size(); j++) {
            dailyMPS.get(j).setUtilizationNormal(utilNorm.get(j));
            dailyMPS.get(j).setUtilizationOverTime(utilOT.get(j));
        }
        //set timer to wait for customer's reply
        scheduleTimer(backOrder.getId());
        return earliest;
    }

    private Date assignReservedCapacity(BackorderEntity backOrder, ArrayList<ProductQuantityPriceEntity> pqp, ArrayList<MasterProductionScheduleDailyEntity> dailyMPS) {
        Date earliest = new Date();
        System.out.println("ATP: Starting ATP check...");
        //for each of the product, get its quantity
        for (int i = 0; i < pqp.size(); i++) {
            //if quantity != 0, insert these quantities into each of the daily productions from now on
            if (pqp.get(i).getQuantity() != 0) {
                //for each dailyMPS
                for (int j = 0; j < dailyMPS.size(); j++) {
                    if (pqp.get(i).getQuantity() != 0) {
                        double reservedCapacity = 0;
                        calculateUtil(dailyMPS.get(j));
                        //check if the current mps is already max out at 95% at OT level
                        //rationale = to provide goods asap, so always max out at 95% OT level to get goods asap
                        if (dailyMPS.get(j).getUtilizationOverTime() < 95) {
                            System.out.println("ATP: " + dailyMPS.get(j).getDayofProduction() + " " + dailyMPS.get(j).getMpsMonthly().getMonthOfProduction() + " Utilization < 95%");
                            ArrayList<ProductPercentageQuantityEntity> dailyPPQ = new ArrayList<ProductPercentageQuantityEntity>(dailyMPS.get(j).getBreakdownByProduct());
                            //for each of this dailyMPS, get its ppq
                            for (int k = 0; k < dailyPPQ.size(); k++) {
                                //find the product that matches the product of the pqp out of the 4 products from ppq
                                if (dailyPPQ.get(k).getProduct().getId() == pqp.get(i).getProduct().getId()) {
                                    System.out.println("ATP: Found matched product = " + dailyPPQ.get(k).getProduct().getProductName());
                                    //add ALL of pqp quantity to ppq's original quantity
                                    dailyPPQ.get(k).setQuantity(dailyPPQ.get(k).getQuantity() + pqp.get(i).getQuantity());
                                    System.out.println("ATP: PPQ original quantity = " + (dailyPPQ.get(k).getQuantity() - pqp.get(i).getQuantity()) + " PQP original quantity = " + pqp.get(i).getQuantity() + " New quantity = " + dailyPPQ.get(k).getQuantity());
                                    //reserve this amount of capacity, calculate the capacity required
                                    double capacityRequired = (1.0 / pqp.get(i).getProduct().getCasesOfHour()) * pqp.get(i).getQuantity();
                                    System.out.println("ATP: Capacity required for this set of quantity = " + capacityRequired);
                                    reservedCapacity = reservedCapacity + capacityRequired;
                                    System.out.println("ATP: New reservedCapacity = " + reservedCapacity);
                                    System.out.println("ATP: Calculating new utilization...");
                                    //calculate the new utilization level while considering change over time
                                    calculateUtil(dailyMPS.get(j));
                                    //check if new OT utilization is less than or equal 95%
                                    if (dailyMPS.get(j).getUtilizationOverTime() <= 95) {
                                        System.out.println("ATP: New OT utilization less than or equal to 95%");
                                        System.out.println("ATP: All quantity for product set, pqp set to 0");
                                        //there are no more quantity in this product
                                        pqp.get(i).setQuantity(0);
                                        System.out.println("ATP: Adding reservedCapacity to daily MPS...");
                                        //add reserved quantity to daily mps
                                        dailyMPS.get(j).setReservedCapacity(dailyMPS.get(j).getReservedCapacity() + reservedCapacity);
                                        //indicate how much was reserved and from where in backorder
                                        if (backOrder.getReservedCapacity().containsKey(dailyMPS.get(j).getId()))
                                            backOrder.getReservedCapacity().put(dailyMPS.get(j).getId(), backOrder.getReservedCapacity().get(dailyMPS.get(j).getId()) + reservedCapacity);
                                        else 
                                            backOrder.getReservedCapacity().put(dailyMPS.get(j).getId(), reservedCapacity);
                                        Date fulfilledDate = new Date(dailyMPS.get(j).getYearOfProduction() - 1900, dailyMPS.get(j).getMpsMonthly().getMonthOfProduction().getMonthNum() - 1, dailyMPS.get(j).getDayofProduction());
                                        if (earliest.before(fulfilledDate)) {
                                            earliest = fulfilledDate;
                                            System.out.println("ATP: Temp Lead Time = " + earliest);
                                        }
                                            break;
                                    } else {
                                        System.out.println("ATP: New OT utilization more than 95%");
                                        //check overshot by how much percent
                                        double percentageOvershot = dailyMPS.get(j).getUtilizationOverTime() - 95;
                                        System.out.println("ATP: Overshot percentage = " + percentageOvershot);
                                        //get capacity hrs from percentage, overshot = overshotPercent * total
                                        double overshotCapacity = (percentageOvershot / 100) * dailyMPS.get(j).getCapacityOverTime();
                                        System.out.println("ATP: Overshot capacity = " + overshotCapacity);
                                        reservedCapacity = reservedCapacity - overshotCapacity;
                                        System.out.println("ATP: Overshot reserved = " + reservedCapacity);
                                        double overshotNumberOfCases = overshotCapacity * pqp.get(i).getProduct().getCasesOfHour();
                                        System.out.println("ATP: Overshot number of cases = " + overshotNumberOfCases);
                                        //pqp will continue to contain that overshot quantity
                                        pqp.get(i).setQuantity((long) overshotNumberOfCases);
                                        dailyMPS.get(j).setReservedCapacity(dailyMPS.get(j).getReservedCapacity() + reservedCapacity);
                                        if (backOrder.getReservedCapacity().containsKey(dailyMPS.get(j).getId()))
                                            backOrder.getReservedCapacity().put(dailyMPS.get(j).getId(), backOrder.getReservedCapacity().get(dailyMPS.get(j).getId()) + reservedCapacity);
                                        else 
                                            backOrder.getReservedCapacity().put(dailyMPS.get(j).getId(), reservedCapacity);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return earliest;
    }

    //Production ATP
    private Date getProductionStartDate(Date earliestRMReceipt) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(earliestRMReceipt);
        //production to start next day
        cal.add(Calendar.DATE, 1);
        Date productionStartDate = cal.getTime();
        return productionStartDate;
    }

    //Production ATP
    private ArrayList<MasterProductionScheduleDailyEntity> getAllDailyMPSFromDateOnwards(Date productionStartDate) {
        ArrayList<MasterProductionScheduleDailyEntity> dailyMPS = new ArrayList<MasterProductionScheduleDailyEntity>();
        //get all dailymps
        Query query = em.createQuery("SELECT d FROM MasterProductionScheduleDaily d INNER JOIN d.mpsMonthly m WHERE d.archived!= 'true' ORDER BY m.monthOfProduction, d.dayofProduction");
        for (Object o : query.getResultList()) {
            MasterProductionScheduleDailyEntity m = (MasterProductionScheduleDailyEntity) o;
            Date date = new Date(m.getYearOfProduction() - 1900, m.getMpsMonthly().getMonthOfProduction().getMonthNum() - 1, m.getDayofProduction());
            System.out.println(date);
            if (!date.before(productionStartDate)) {
                dailyMPS.add(m);
            }
        }
        return dailyMPS;
    }

    //Production ATP
    private void calculateUtil(MasterProductionScheduleDailyEntity dailyMPS) {
        System.out.println("ATP: calculating daily utilization");
        ArrayList<ProductPercentageQuantityEntity> ppq = new ArrayList<ProductPercentageQuantityEntity>(dailyMPS.getBreakdownByProduct());
        double util = 0;
        for (int i = 0; i < ppq.size(); i++) {
            long quantity = ppq.get(i).getQuantity();
            util = util + ((1.0 / ppq.get(i).getProduct().getCasesOfHour()) * quantity);
            System.out.println("ATP: quantity=" + quantity + "util=" + util);
        }
        //consider daily change over time
        util = util + dailyMPS.getChangeOverTime();
        //consider reserved capacity
        util = dailyMPS.getReservedCapacity() + util;
        double maxNormal = (dailyMPS.getCapacityNormal() * 0.95);
        double otQuantity = 0;
//        System.out.println("util:" + util + "changeOverTime:" + dailyMPS.getChangeOverTime());
//        System.out.println("maxNormal:" + maxNormal);
        if (util >= maxNormal) {
            otQuantity = Math.ceil(util - maxNormal);
            util = maxNormal;
            dailyMPS.setUtilizationNormal(Math.round((util / dailyMPS.getCapacityNormal()) * 1000) / 10);
            dailyMPS.setUtilizationOverTime(Math.round((otQuantity / dailyMPS.getCapacityNormal()) * 1000) / 10);
        } else {
            dailyMPS.setUtilizationNormal(Math.round((util / dailyMPS.getCapacityNormal()) * 1000) / 10);
            System.out.println(dailyMPS.getUtilizationNormal());
            dailyMPS.setUtilizationOverTime(Math.round((otQuantity / dailyMPS.getCapacityNormal()) * 1000) / 10);
        }
    }

    private void scheduleTimer(long id) {
        Date today = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(today);
        //3 days later
        cal.add(Calendar.DATE, 3);
        today = cal.getTime();
        ctx.getTimerService().createTimer(today, id);
    }

    @Timeout
    private void timeoutHandler(Timer timer) {
        BackorderEntity b = em.find(BackorderEntity.class, timer.getInfo());
        //task to perform when timer expire
        //sop
        cancelReservedCapacity(b);
        //mrp
        cancelMrpReservationForBackOrder(b);
        em.merge(b);
        timer.cancel();
    }

    public void cancelTimer() {
        Collection<Timer> list = ctx.getTimerService().getTimers();
        Iterator<Timer> it = list.iterator();

        while (it.hasNext()) {
            Timer timer = it.next();
            timer.cancel();
        }
    }

    //Production ATP
    public void cancelReservedCapacity(BackorderEntity backOrder) {
        ArrayList<MasterProductionScheduleDailyEntity> possibleMPS = new ArrayList<MasterProductionScheduleDailyEntity>();
        possibleMPS = getAllDailyMPSFromDateOnwards(getProductionStartDate(backOrder.getEarliestMrpFulfillmentDate()));
        for (int i = 0; i < possibleMPS.size(); i++) {
            if (backOrder.getReservedCapacity().containsKey(possibleMPS.get(i).getId())) {
                possibleMPS.get(i).setReservedCapacity(possibleMPS.get(i).getReservedCapacity() - backOrder.getReservedCapacity().get(possibleMPS.get(i).getId()));
                System.out.println("CANCEL ATP: Total Reserved Capacity = " + possibleMPS.get(i).getReservedCapacity() + " Deleting = " + backOrder.getReservedCapacity().get(possibleMPS.get(i).getId()));
                backOrder.getReservedCapacity().remove(possibleMPS.get(i).getId());
                em.merge(possibleMPS.get(i));
            }
        }
        em.merge(backOrder);
    }

    private Date calculateEarliestFulfillmentDateVersion2(BackorderEntity backOrder, ArrayList<RawMaterialEntity> materialList) {
        int earliestWeekNum = 0;

        for (RawMaterialEntity material : materialList) {
            RawMaterialRequirementWeeklyEntity currentMrp = findCurrentWeekMrp(material);
            ArrayList<RawMaterialRequirementWeeklyEntity> inBetweenMrps = findMrpsInBetween(currentMrp);


            if (findEarliestWeekNumber(inBetweenMrps, backOrder) > earliestWeekNum) {
                earliestWeekNum = findEarliestWeekNumber(inBetweenMrps, backOrder);
            }

        }

        if (earliestWeekNum == 1000) {
            return calculateEarliestFulfillmentDateMRP(backOrder, materialList);
        } else {
            Calendar cal = Calendar.getInstance();
            System.out.println("earliestWeekNum " + earliestWeekNum);
            cal.add(Calendar.DATE, earliestWeekNum * 7);

            if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
                cal.add(Calendar.DATE, 2);// shift to monday as the working day
            }
            if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
                cal.add(Calendar.DATE, 3);// shift to monday as the working day
            }

            Date earliest = cal.getTime();
            return earliest;
        }
    }

    private int findEarliestWeekNumber(ArrayList<RawMaterialRequirementWeeklyEntity> inBetweenMrps, BackorderEntity backOrder) {
        RawMaterialEntity material = inBetweenMrps.get(0).getRawMaterial();
        long grossRequirement = calculateGrossRequirement(backOrder, material);
        for (int i = 0; i < inBetweenMrps.size(); i++) {
            if (mrpOkayToDeduct(i, inBetweenMrps, grossRequirement)) {
                return i;
            }
        }

//        System.out.println("return 1000");
        return 1000;
    }

    private void reserveBackOrder(BackorderEntity backOrder, Collection<RawMaterialEntity> list) {
        Date earliestDate = backOrder.getEarliestMrpFulfillmentDate();
        for (RawMaterialEntity material : list) {
            RawMaterialRequirementWeeklyEntity earliestMrp = findMrpBasedOnDate(earliestDate, material);
            long grossRequirement = calculateGrossRequirement(backOrder, material);
            if (earliestMrp != null) {
                earliestMrp.setMrpReservation(grossRequirement);
                RawMaterialRequirementWeeklyEntity nextMrp = findNextWeekMrp(earliestMrp);
                em.merge(earliestMrp);
                MrpToQuantityEntity mtq = new MrpToQuantityEntity(earliestMrp, grossRequirement);
                mtq.setBackOrder3(backOrder);
                em.persist(mtq);
                backOrder.getReservedAtpMrps().add(mtq);
                while (nextMrp != null) {
                    nextMrp.setMrpReservation(grossRequirement);
                    MrpToQuantityEntity mtq2 = new MrpToQuantityEntity(earliestMrp, grossRequirement);
                    mtq.setBackOrder3(backOrder);
                    em.persist(mtq2);
                    backOrder.getReservedAtpMrps().add(mtq2);
                    nextMrp = findNextWeekMrp(nextMrp);
                }
            }
        }
    }

    private RawMaterialRequirementWeeklyEntity findMrpBasedOnDate(Date earliestDate, RawMaterialEntity material) {
        Calendar today = Calendar.getInstance();
        today.setTime(earliestDate);
        int currentYear = today.get(Calendar.YEAR);
        Month currentMonth = Month.fromMonthNum(today.get(Calendar.MONTH) + 1);
        int currentWeek = today.get(Calendar.WEEK_OF_MONTH);
        Calendar testCurrent = Calendar.getInstance();
        testCurrent.set(currentYear, today.get(Calendar.MONTH), 1);
        if (!checkFirstDayOfMonthIsMondayOrSunday(testCurrent)) {
            currentWeek -= 1;
            if (currentWeek == 0) { // means it's previous month's last week
                currentWeek = findPreviousMonthLastWeekNum(testCurrent);
                if (currentMonth != Month.JANUARY) {
                    currentMonth = Month.fromMonthNum(currentMonth.getMonthNum() - 1);
                } else {
                    currentYear -= 1;
                    currentMonth = Month.DECEMBER;
                }
            }
//          System.out.println("currentWeekNum is "+currentWeek);
        } else {
//           System.out.println("currentWeekNum is "+currentWeek);
        }

        return findMrp(currentYear, currentMonth, currentWeek, material);
    }

    private boolean mrpOkayToDeduct(int i, ArrayList<RawMaterialRequirementWeeklyEntity> inBetweenMrps, long grossRequirement) {
        for (int k = i; k < inBetweenMrps.size(); k++) {
            if (inBetweenMrps.get(k).getOnHandQuantity() - inBetweenMrps.get(k).getMrpReservation() - grossRequirement < 0) {
//                System.out.println("getK.quantity "+inBetweenMrps.get(k).getOnHandQuantity());
//                System.out.println("grossRequirement "+grossRequirement);
//                System.out.println(inBetweenMrps.get(k).getOnHandQuantity()-inBetweenMrps.get(k).getMrpReservation()-grossRequirement);
//                System.out.println("false");
                return false;
            }
        }
//        System.out.println("true");
        return true;
    }

    public void cancelMrpReservationForBackOrder(BackorderEntity current) {
        ArrayList<MrpToQuantityEntity> mtqList = new ArrayList<MrpToQuantityEntity>(current.getReservedAtpMrps());
        if (!mtqList.isEmpty()) {
            for (MrpToQuantityEntity mtq : mtqList) {
                RawMaterialRequirementWeeklyEntity mrp = mtq.getMrp();
                mrp.setMrpReservation(mrp.getMrpReservation() - mtq.getQuantity());
                em.merge(mrp);
            }
        }
        current.setReservedAtpMrps(new ArrayList<MrpToQuantityEntity>());
//        timer.cancel();
        em.merge(current);
    }

    //Production ATP
    public void atpForProductionApproved(BackorderEntity backOrder, Date earliestRMReceipt) { //aim of our atp is to always provide the earliest date
        cancelReservedCapacity(backOrder);
        System.out.println("ATP: Preparing ATP check...");
        //holding list to reset the revised values back to the backorder's original values
        ArrayList<Long> quantity = new ArrayList<Long>();
        ArrayList<Double> utilNorm = new ArrayList<Double>();
        ArrayList<Double> utilOT = new ArrayList<Double>();

        //the earliest date production may start after ordering the goods
        Date productionStartDate = getProductionStartDate(earliestRMReceipt);
        //get all daily mps from that day onwards
        ArrayList<MasterProductionScheduleDailyEntity> dailyMPS = getAllDailyMPSFromDateOnwards(productionStartDate);
        //get pqp of back order
        ArrayList<ProductQuantityPriceEntity> pqp = new ArrayList<ProductQuantityPriceEntity>(backOrder.getProductQuantityPriceList());
        //store them into a holding list
        for (int i = 0; i < pqp.size(); i++) {
            long q = pqp.get(i).getQuantity();
            quantity.add(q);
        }
        for (int h = 0; h < dailyMPS.size(); h++) {
            double norm = dailyMPS.get(h).getUtilizationNormal();
            double ot = dailyMPS.get(h).getUtilizationOverTime();
            utilNorm.add(norm);
            utilOT.add(ot);
        }
        assignProductionCapacity(backOrder, pqp, dailyMPS);
        //reset all values changed
        for (int i = 0; i < quantity.size(); i++) {
            pqp.get(i).setQuantity(quantity.get(i));
        }
//        for (int j = 0; j < dailyMPS.size(); j++) {
//            dailyMPS.get(j).setUtilizationNormal(utilNorm.get(j));
//            dailyMPS.get(j).setUtilizationOverTime(utilOT.get(j));
//        }
    }
    
    public void assignProductionCapacity(BackorderEntity backOrder, ArrayList<ProductQuantityPriceEntity> pqp, ArrayList<MasterProductionScheduleDailyEntity> dailyMPS) {
        System.out.println("ATP: Starting ATP check...");
        //for each of the product, get its quantity
        for (int i = 0; i < pqp.size(); i++) {
            //if quantity != 0, insert these quantities into each of the daily productions from now on
            if (pqp.get(i).getQuantity() != 0) {
                //for each dailyMPS
                for (int j = 0; j < dailyMPS.size(); j++) {
                    if (pqp.get(i).getQuantity() != 0) {
                        double reservedCapacity = 0;
                        calculateUtil(dailyMPS.get(j));
                        //check if the current mps is already max out at 95% at OT level
                        //rationale = to provide goods asap, so always max out at 95% OT level to get goods asap
                        if (dailyMPS.get(j).getUtilizationOverTime() < 95) {
                            System.out.println("ATP: " + dailyMPS.get(j).getDayofProduction() + " " + dailyMPS.get(j).getMpsMonthly().getMonthOfProduction() + " Utilization < 95%");
                            ArrayList<ProductPercentageQuantityEntity> dailyPPQ = new ArrayList<ProductPercentageQuantityEntity>(dailyMPS.get(j).getBreakdownByProduct());
                            //for each of this dailyMPS, get its ppq
                            for (int k = 0; k < dailyPPQ.size(); k++) {
                                //find the product that matches the product of the pqp out of the 4 products from ppq
                                if (dailyPPQ.get(k).getProduct().getId() == pqp.get(i).getProduct().getId()) {
                                    System.out.println("ATP: Found matched product = " + dailyPPQ.get(k).getProduct().getProductName());
                                    //add ALL of pqp quantity to ppq's original quantity
                                    dailyPPQ.get(k).setQuantity(dailyPPQ.get(k).getQuantity() + pqp.get(i).getQuantity());
                                    System.out.println("ATP: PPQ original quantity = " + (dailyPPQ.get(k).getQuantity() - pqp.get(i).getQuantity()) + " PQP original quantity = " + pqp.get(i).getQuantity() + " New quantity = " + dailyPPQ.get(k).getQuantity());
                                    //reserve this amount of capacity, calculate the capacity required
                                    double capacityRequired = (1.0 / pqp.get(i).getProduct().getCasesOfHour()) * pqp.get(i).getQuantity();
                                    System.out.println("ATP: Capacity required for this set of quantity = " + capacityRequired);
                                    reservedCapacity = reservedCapacity + capacityRequired;
                                    System.out.println("ATP: New reservedCapacity = " + reservedCapacity);
                                    System.out.println("ATP: Calculating new utilization...");
                                    //calculate the new utilization level while considering change over time
                                    calculateUtil(dailyMPS.get(j));
                                    //check if new OT utilization is less than or equal 95%
                                    if (dailyMPS.get(j).getUtilizationOverTime() <= 95) {
                                        System.out.println("ATP: New OT utilization less than or equal to 95%");
                                        System.out.println("ATP: All quantity for product set, pqp set to 0");
                                        //there are no more quantity in this product
                                        pqp.get(i).setQuantity(0);
                                        System.out.println("ATP: Adding reservedCapacity to daily MPS...");
                                        //add reserved quantity to daily mps
                                        updateIndividualQuantity(dailyMPS.get(j));
                                        em.merge(updateIndividualQuantity(dailyMPS.get(j)));
                                        //indicate how much was reserved and from where in backorder
                                        //break;
                                        
                                    } else {
                                        System.out.println("ATP: New OT utilization more than 95%");
                                        //check overshot by how much percent
                                        double percentageOvershot = dailyMPS.get(j).getUtilizationOverTime() - 95;
                                        System.out.println("ATP: Overshot percentage = " + percentageOvershot);
                                        //get capacity hrs from percentage, overshot = overshotPercent * total
                                        double overshotCapacity = (percentageOvershot / 100) * dailyMPS.get(j).getCapacityOverTime();
                                        System.out.println("ATP: Overshot capacity = " + overshotCapacity);
                                        reservedCapacity = reservedCapacity - overshotCapacity;
                                        System.out.println("ATP: Overshot reserved = " + reservedCapacity);
                                        double overshotNumberOfCases = overshotCapacity * pqp.get(i).getProduct().getCasesOfHour();
                                        System.out.println("ATP: Overshot number of cases = " + overshotNumberOfCases);
                                        dailyPPQ.get(k).setQuantity((long) (dailyPPQ.get(k).getQuantity() - overshotNumberOfCases));
                                        calculateUtil(dailyMPS.get(j));
                                        //pqp will continue to contain that overshot quantity
                                        pqp.get(i).setQuantity((long) overshotNumberOfCases);
                                        
                                        em.merge(updateIndividualQuantity(dailyMPS.get(j)));
                                        //break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    private MasterProductionScheduleDailyEntity updateIndividualQuantity (MasterProductionScheduleDailyEntity dailyMPS) {
        System.out.println("Setting PPQ to individual products");
        ArrayList<ProductPercentageQuantityEntity> ppq = new ArrayList<ProductPercentageQuantityEntity>(dailyMPS.getBreakdownByProduct());
        for (int i = 0; i < ppq.size(); i++) {
            if (ppq.get(i).getProduct().getProductName().equals("Classic Nutri Bar (Nuts)")) {
                dailyMPS.setClassicNuts(ppq.get(i).getQuantity());
                System.out.println("in classic nuts");
            }
            if (ppq.get(i).getProduct().getProductName().equals("Classic Nutri Bar (Fruits)")) {
                dailyMPS.setClassicFruits(ppq.get(i).getQuantity());
                System.out.println("in classic Fruits");
            }
            if (ppq.get(i).getProduct().getProductName().equals("Premium Power Bar (Chocolate Nuts)")) {
                dailyMPS.setPremiumNuts(ppq.get(i).getQuantity());
                System.out.println("in premium nuts");
            }
            if (ppq.get(i).getProduct().getProductName().equals("Premium Power Bar (Redcurrant Fruits)")) {
                dailyMPS.setPremiumFruits(ppq.get(i).getQuantity());
                System.out.println("in premium fruits");
            }
        }
        return dailyMPS;
    }

    //Production ATP
    public Date atpCheckForQuotation(SalesQuotationEntity salesQuotation) { //aim of our atp is to always provide the earliest date
        Date indicativeLeadTime = new Date();
        System.out.println("ATP: Preparing ATP check...");
        //holding list to reset the revised values back to the backorder's original values
        ArrayList<Long> quantity = new ArrayList<Long>();
        ArrayList<Double> utilNorm = new ArrayList<Double>();
        ArrayList<Double> utilOT = new ArrayList<Double>();
        //the earliest date production may start after ordering the goods
        Date productionStartDate = getProductionStartDate(indicativeLeadTime);
        ArrayList<MasterProductionScheduleDailyEntity> dailyMPS = getAllDailyMPSFromDateOnwards(productionStartDate);
        //get pqp of back order
        ArrayList<ProductQuantityPriceEntity> pqp = new ArrayList<ProductQuantityPriceEntity>(salesQuotation.getPqp());
        //store them into a holding list
        for (int i = 0; i < pqp.size(); i++) {
            long q = pqp.get(i).getQuantity();
            quantity.add(q);
        }
        
        indicativeLeadTime = assignLeadTime(salesQuotation, pqp, dailyMPS);
        salesQuotation.setIndicativeLeadTime(indicativeLeadTime);
        //reset all values changed
        for (int i = 0; i < quantity.size(); i++) {
            pqp.get(i).setQuantity(quantity.get(i));
        }

        return indicativeLeadTime;
    }
    
    private Date assignLeadTime(SalesQuotationEntity salesQuotation, ArrayList<ProductQuantityPriceEntity> pqp, ArrayList<MasterProductionScheduleDailyEntity> dailyMPS) {
        Date indicativeLeadTime = new Date();
        System.out.println("ATP: Starting ATP check...");
        //for each of the product, get its quantity
        
        for (int i = 0; i < pqp.size(); i++) {
            //if quantity != 0, insert these quantities into each of the daily productions from now on
            if (pqp.get(i).getQuantity() != 0) {
                //for each dailyMPS
                for (int j = 0; j < dailyMPS.size(); j++) {
                    if (pqp.get(i).getQuantity() != 0) {
                        double reservedCapacity = 0;
                        calculateUtil(dailyMPS.get(j));
                        //check if the current mps is already max out at 95% at OT level
                        //rationale = to provide goods asap, so always max out at 95% OT level to get goods asap
                        if (dailyMPS.get(j).getUtilizationOverTime() < 95) {
                            System.out.println("ATP: " + dailyMPS.get(j).getDayofProduction() + " " + dailyMPS.get(j).getMpsMonthly().getMonthOfProduction() + " Utilization < 95%");
                            ArrayList<ProductPercentageQuantityEntity> dailyPPQ = new ArrayList<ProductPercentageQuantityEntity>(dailyMPS.get(j).getBreakdownByProduct());
                            //for each of this dailyMPS, get its ppq
                            for (int k = 0; k < dailyPPQ.size(); k++) {
                                //find the product that matches the product of the pqp out of the 4 products from ppq
                                if (dailyPPQ.get(k).getProduct().getId() == pqp.get(i).getProduct().getId()) {
                                    System.out.println("ATP: Found matched product = " + dailyPPQ.get(k).getProduct().getProductName());
                                    //add ALL of pqp quantity to ppq's original quantity
                                    dailyPPQ.get(k).setQuantity(dailyPPQ.get(k).getQuantity() + pqp.get(i).getQuantity());
                                    System.out.println("ATP: PPQ original quantity = " + (dailyPPQ.get(k).getQuantity() - pqp.get(i).getQuantity()) + " PQP original quantity = " + pqp.get(i).getQuantity() + " New quantity = " + dailyPPQ.get(k).getQuantity());
                                    //reserve this amount of capacity, calculate the capacity required
                                    double capacityRequired = (1.0 / pqp.get(i).getProduct().getCasesOfHour()) * pqp.get(i).getQuantity();
                                    System.out.println("ATP: Capacity required for this set of quantity = " + capacityRequired);
                                    reservedCapacity = reservedCapacity + capacityRequired;
                                    System.out.println("ATP: New reservedCapacity = " + reservedCapacity);
                                    System.out.println("ATP: Calculating new utilization...");
                                    //calculate the new utilization level while considering change over time
                                    calculateUtil(dailyMPS.get(j));
                                    //check if new OT utilization is less than or equal 95%
                                    if (dailyMPS.get(j).getUtilizationOverTime() <= 95) {
                                        System.out.println("ATP: New OT utilization less than or equal to 95%");
                                        System.out.println("ATP: All quantity for product set, pqp set to 0");
                                        //there are no more quantity in this product
                                        pqp.get(i).setQuantity(0);
                                        System.out.println("ATP: Adding reservedCapacity to daily MPS...");
                                        //add reserved quantity to daily mps
                                        dailyMPS.get(j).setReservedCapacity(dailyMPS.get(j).getReservedCapacity() + reservedCapacity);
                                        //indicate how much was reserved and from where in backorder
                                        
                                        Date fulfilledDate = new Date(dailyMPS.get(j).getYearOfProduction() - 1900, dailyMPS.get(j).getMpsMonthly().getMonthOfProduction().getMonthNum() - 1, dailyMPS.get(j).getDayofProduction());
                                        if (indicativeLeadTime.before(fulfilledDate)) {
                                            indicativeLeadTime = fulfilledDate;
                                            System.out.println("ATP: Temp Lead Time = " + indicativeLeadTime);
                                        }
                                            
                                    } else {
                                        System.out.println("ATP: New OT utilization more than 95%");
                                        //check overshot by how much percent
                                        double percentageOvershot = dailyMPS.get(j).getUtilizationOverTime() - 95;
                                        System.out.println("ATP: Overshot percentage = " + percentageOvershot);
                                        //get capacity hrs from percentage, overshot = overshotPercent * total
                                        double overshotCapacity = (percentageOvershot / 100) * dailyMPS.get(j).getCapacityOverTime();
                                        System.out.println("ATP: Overshot capacity = " + overshotCapacity);
                                        reservedCapacity = reservedCapacity - overshotCapacity;
                                        System.out.println("ATP: Overshot reserved = " + reservedCapacity);
                                        double overshotNumberOfCases = overshotCapacity * pqp.get(i).getProduct().getCasesOfHour();
                                        System.out.println("ATP: Overshot number of cases = " + overshotNumberOfCases);
                                        //pqp will continue to contain that overshot quantity
                                        pqp.get(i).setQuantity((long) overshotNumberOfCases);
                                        dailyMPS.get(j).setReservedCapacity(dailyMPS.get(j).getReservedCapacity() + reservedCapacity);
                                        
                                        
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return indicativeLeadTime;
    }
            
    public void createSecondShippingRequest(BackorderEntity backOrder) {
        ArrayList<ShippingRequestEntity> shippingRequests = new ArrayList<ShippingRequestEntity>(backOrder.getSalesOrder().getPurchaseOrder().getShippingRequests());
        ArrayList<ShippingRequestEntity> newshippingRequests = new ArrayList<ShippingRequestEntity>();
        for (int i = 0; i < shippingRequests.size(); i++) {
            ShippingRequestEntity request = new ShippingRequestEntity();
            request.setProduct(shippingRequests.get(i).getProduct());
            request.setQuantity(0);
            request.setShippingDate(backOrder.getConfirmedLeadTime());
            request.setAddress(shippingRequests.get(i).getAddress());
            newshippingRequests.add(request);
        }
        for (int k = 0; k < shippingRequests.size(); k++) {
            shippingRequests.get(k).setShippingDate(backOrder.getRequiredLeadTime());
        }
        for (int j = 0; j < newshippingRequests.size(); j++) {
            shippingRequests.add(newshippingRequests.get(j));
        }
        backOrder.getSalesOrder().getPurchaseOrder().setShippingRequests(shippingRequests);
        em.merge(backOrder.getSalesOrder().getPurchaseOrder());
    }
    
    public void setStatus(SalesOrderEntity salesOrder) {
        salesOrder.setInternalStatus(InternalStatus.ProvideLeadTimePending);
        em.merge(salesOrder);
    }
}
